package com.liaoyifan.core.util;

import com.liaoyifan.core.constant.Constants;
import com.liaoyifan.core.constant.Properties;
import com.liaoyifan.core.valid.Assert;
import jakarta.annotation.PostConstruct;
import java.time.Duration;
import java.util.Map;
import lombok.extern.log4j.Log4j2;
import org.redisson.api.RAtomicLong;
import org.redisson.api.RList;
import org.redisson.api.RLock;
import org.redisson.api.RMap;
import org.redisson.api.RRateLimiter;
import org.redisson.api.RSet;
import org.redisson.api.RateType;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;

/**
 * @author liaoyifan
 */
@SuppressWarnings("unused")
@Log4j2
@ConditionalOnProperty(name = Properties.REDIS_ENABLE, havingValue = Constants.TRUE)
public class Redis {

    @Autowired private RedissonClient redisson;

    private static Redis redis;

    @PostConstruct
    public void init() {
        redis = this;
    }

    public static RedissonClient redisson() {
        return redis.redisson;
    }

    @SuppressWarnings("UnusedReturnValue")
    public static long delete(String... keys) {
        if (keys == null || keys.length == 0) {
            return 0;
        }
        return redisson().getKeys().delete(keys);
    }

    public static <T> T get(String key) {
        Assert.notBlank(key, "key cannot be blank");
        return redisson().<T>getBucket(key).get();
    }

    public static RLock getLock(String key) {
        Assert.notBlank(key, "key cannot be blank");
        return redisson().getLock(key);
    }

    public static RLock getMultiLock(RLock... array) {
        Assert.notNull(array, "array cannot be null");
        return redisson().getMultiLock(array);
    }

    public static <V> RSet<V> getSet(String key) {
        Assert.notBlank(key, "key cannot be blank");
        return redisson().getSet(key);
    }

    public static <K, V> RMap<K, V> getMap(String key) {
        Assert.notBlank(key, "key cannot be blank");
        return redisson().getMap(key);
    }

    public static <V> RList<V> getList(String key) {
        Assert.notBlank(key, "key cannot be blank");
        return redisson().getList(key);
    }

    public static RAtomicLong getAtomicLong(String key) {
        Assert.notBlank(key, "key cannot be blank");
        return redisson().getAtomicLong(key);
    }

    public static <T> void set(String key, T value, Duration duration) {
        Assert.notBlank(key, "key cannot be blank");
        Assert.notNull(duration, "duration cannot be null");
        redisson().<T>getBucket(key).set(value, duration);
    }

    public static <T> void expire(String key, Duration duration) {
        Assert.notBlank(key, "key cannot be blank");
        Assert.notNull(duration, "value cannot be null");
        redisson().getBucket(key).expire(duration);
    }

    public static <T> boolean setIfAbsent(String key, T value, Duration duration) {
        Assert.notBlank(key, "key cannot be blank");
        Assert.notNull(value, "value cannot be null");
        Assert.notNull(duration, "duration cannot be null");
        return redisson().<T>getBucket(key).setIfAbsent(value, duration);
    }

    public static boolean trySet(Map<String, Object> map, Duration duration) {
        Assert.notEmpty(map, "map cannot be empty");
        boolean success = redisson().getBuckets().trySet(map);
        map.keySet().forEach(key -> expire(key, duration));
        return success;
    }

    @SuppressWarnings("BooleanMethodIsAlwaysInverted")
    public static boolean tryAcquire(
            String key, long allowed, long rate, long acquire, long timeout) {
        RRateLimiter limiter = rateLimiter(key, allowed, Duration.ofSeconds(rate));
        boolean success = limiter.tryAcquire(acquire, Duration.ofSeconds(timeout));
        if (success) {
            renewal(limiter);
        }
        return success;
    }

    public static void acquire(String key, long allowed, long rate, long acquire) {
        RRateLimiter limiter = rateLimiter(key, allowed, Duration.ofSeconds(rate));
        limiter.acquire(acquire);
        renewal(limiter);
    }

    private static RRateLimiter rateLimiter(String key, long allowed, Duration rate) {
        Assert.notBlank(key, "key cannot be blank");
        Assert.notNull(rate, "rate cannot be null");
        RRateLimiter limiter = redisson().getRateLimiter(key);
        if (!limiter.isExists()) {
            limiter.trySetRate(
                    RateType.OVERALL, // 全局限制(所有实例共享)
                    allowed, // 总许可数(单位时间内允许数量)
                    rate // 窗口大小(单位时间)
                    );
            limiter.expire(Duration.ofHours(1));
        }
        return limiter;
    }

    private static void renewal(RRateLimiter limiter) {
        Assert.notNull(limiter, "limiter cannot be null");
        // 若剩余时间小于10分钟，则续期1小时
        if (limiter.remainTimeToLive() < 10 * 60 * 1000) {
            limiter.expire(Duration.ofHours(1));
        }
    }
}
